package coreModel;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import hitExceptions.InvalidDataException;

/**
 * This class will represent a given barcode.
 * Some info about barcodes:
 * 		UPC-A is 12 digits long
 * 		Only 11 digits are usable because the last digit is used as a check digit to ensure accuracy
 * 		The barcodes that we read in do not need to have the check digit evaluated
 * 
 * @author The Fantastic 4
 */
public class Barcode extends SerializableImplementer
{
	private String value;
	
	private final static int BC_LEN = 12;
	
	/**
	 * Construct a barcode with a random, but valid UPC
	 */
	public Barcode ()
	{
		value = Barcode.generateBarcode();
	}
			
	/**
	 * Construct a new barcode from a string.
	 * @param value is a string containing a barcode number
	 */	
	public Barcode(String value)
	{
		this.value = new String(value);
		
//		this.setValue(value);
	}
	
	/**
	 * @return The string of numbers stored in the object.
	 */
	public String getValue()
	{
		return new String (value) ;
	}
	
	/**
	 * Assign the barcode a new value
	 * @param value The string to be stored in the barcode
	 */
	public void setValue(String value)
	{
//		String tmpV = value.trim();
//		
//		if ( ! isValidBarcode(tmpV) )
//			throw new InvalidDataException("That is not a valid barcode, buddy!");
//		
//		
//		tmpV = stripLeadingZeros(tmpV);			// Leading zeros are stripped because the user
//												// could give too many or too few.
//		tmpV = addLeadingZeros(tmpV);			// After we strip them we get the correct number
//												// of leading zeros.
//		this.value = new String (tmpV);
		this.value = new String (value);
	}
	
	/**
	 * This will get the number of leading zeros that the given barcode number needs
	 * @param value the number that needs leading zeros added to it.
	 * @return a String of the given value with the needed leading zeros added.
	 */
	private static String addLeadingZeros(String value)
	{
		String result = "";
		
		int numZeros = BC_LEN - value.length();
		
		for (int i=0; i<numZeros; i++)
		{
			result = result + "0";
		}
		
		result = result + value;
		
		return result;
	}

	/**
	 * @param value the barcode  number to be checked for validity
	 * @return True if the value is a valid barcode 
	 */
	public static boolean isValidBarcode(String value)
	{
		String tmpV = value.trim();
		
		Long tmpL;							
		try									// A barcode is invalid if it is not a valid 'long' ...
		{
			tmpL = new Long(tmpV);
		}
		catch (NumberFormatException e)
		{
			return false;
		}
		
		tmpV = stripLeadingZeros(tmpV);
		if (tmpV.length() > BC_LEN)			// ... and if it is is longer than 12 digits
		{
			return false;
		}
		
		return true; 
	}
	
	/**
	 * This method is used to strip the leading zeros on the barcode value provided by the user.
	 * Why is this necessary? The user could possibly provide too many or too few zeros and
	 * mess up the algorithm for assigning a new value to a barcode.
	 * @param value the user provided barcode that needs to have the leading zeros stripped
	 * @return a new String of the provided value with any leading zeros stripped off
	 */
	private static String stripLeadingZeros(String value)
	{
		String tmpV = value.trim();
		Long tmpL = new Long(0);
		
		try
		{
			tmpL = new Long (tmpV);
		}
		catch (NumberFormatException e)
		{
			return tmpV;
		}
		
		tmpV = tmpL.toString();
		
		return tmpV;
	}
	
	/**
	 * This static method will generate a new valid UPC barcode for the creation of a new
	 * Barcode object. 
	 * *Note: Because this method produces a random barcode there is a chance (about 2 per million)
	 * that there could be duplicate barcodes generated. New barcodes should be checked in the 
	 * ItemsManager to be sure that there are no conflicts.
	 * @return a String with a random barcode.
	 */
	public static String generateBarcode()
	{	
		int [] tmpBC = new int [BC_LEN];
		Random rand = new Random();
		int rndNum;
		int min = 0;
		int max = 9;
		
		
		for (int i=0; i<(BC_LEN-1); i++)
		{
			rndNum = rand.nextInt(max - min + 1) + min;
			tmpBC[i] = rndNum;
		}
		
		int checkDigit;
		
		checkDigit = (tmpBC[0] + tmpBC[2] + tmpBC[4] + tmpBC[6] + tmpBC[8] + tmpBC[10]) * 3;
		checkDigit = checkDigit + tmpBC[1] + tmpBC[3] + tmpBC[5] + tmpBC[7] + tmpBC[9];
		checkDigit = checkDigit % 10;
		if (checkDigit != 0)
			checkDigit = 10 - checkDigit;
		
		tmpBC[11] = checkDigit;
		
		
		String tmpV = "";
		
		for (int i=0; i<BC_LEN; i++ )
		{
			tmpV = tmpV + tmpBC[i];
		}
		
		return tmpV;
	}
	
	
	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((value == null) ? 0 : value.hashCode());
		return result;
	}
	
	/**
	 * @param obj the barcode to be compared for eqaulity
	 * @return True if the barcodes are equal
	 */
	public boolean equals(Object obj) 
	{
		Barcode tmp = (Barcode)obj;
		return this.value.equals(tmp.getValue());
	}
	
	/**
	 * @return a String representation of the barcode
	 */
	public String toString()
	{
		return new String (value);
	}
	
	
	/*
	 * **********************************************************************************
	 * This section is for methods that test functionality of the class
	 * **********************************************************************************
	 */
	
	public static boolean testGenerateBarcode()
	{
		System.out.println("\nTesting generateBarcode():");
		
		Set<String> codes = new HashSet<String>();
		boolean result;
		int dupCount = 0;
		final long TEST_MAX = 100000;
		String tmpBC;
		
		for (long i=0; i<TEST_MAX; i++)
		{
			tmpBC = Barcode.generateBarcode();
			result = codes.add(tmpBC);
			
			if (!result)
			{
				dupCount++;
			}
		}
		
		System.out.println("Sample size of test: " + TEST_MAX);
		System.out.println("Number of duplicates detected: " + dupCount);
		
		if (dupCount == 0)
		{
			System.out.println("Passed!");
			return true;
		}
		else
		{
			System.out.println("FAILED!!");
			return false;
		}
	}
	
	public static boolean testIsValidBarcode()
	{
		System.out.println("\nTesting isValidBarcode(String value): ");
		
		boolean result = false;
		
		String tmp = "";
		
test_loop:
		for (int i = 1; i<=BC_LEN; i++)
		{
			tmp = tmp + "5";
			
			result = isValidBarcode(tmp);
						
			if (!result)
			{
				System.out.println("Failed on length: " + i);
				break test_loop;
			}
			
			System.out.println(tmp);
		}
		
		System.out.println("Test Successful? " + result);
		
		return result;
	}
	/*
	public static boolean testSetValue()
	{
		System.out.println( "\nTesting setValue(String value): " );
		
		int testNo = 1;
		Barcode bc;
		boolean result = true;
		
		System.out.println("Part 1 testing: Leading Zeros");
		try
		{
			String num = "";
			
			for ( ; testNo<=BC_LEN; testNo++)
			{
				num = num + "7";
				bc = new Barcode (num);
				System.out.println(bc.getValue());
			}
		}
		catch (InvalidDataException e)
		{
			System.out.println("Failed on test number: " + testNo);
			System.out.println(e);
			result = false;
		}
		
		if (result)
			System.out.println("Passed!");
		
		
		System.out.println("\nPart 2 testing: Exceptions");
		
		String tmp;
		
		tmp = "Hello";
		System.out.println("Testing: " + tmp);
		try
		{		
			bc = new Barcode( tmp);
			System.out.println("FAILED!!");
			result = false;		
		}
		catch (InvalidDataException e)
		{
			System.out.println("Passed");		
		}
		
		tmp = "1234567890123";
		System.out.println("Testing: " + tmp);
		try
		{
			bc = new Barcode( tmp);
			System.out.println("FAILED!!");
			result = false;
		}
		catch (InvalidDataException e)
		{
			System.out.println("Passed" );
		}
		
		System.out.println( "Test Successful? " + result);
		
		return result;
	}
	*/
	public static boolean testGetValue()
	{
		System.out.println("\nTesting getValue()");
		
		boolean result = true;
		String tmp = "";
		Barcode bc;
		
		tmp = "1234";
		System.out.println(tmp);
		try	{	bc = new Barcode(tmp); System.out.println( bc.getValue() ); }
		catch (Exception e) { return false;	}
		if ( ! bc.getValue().equals(addLeadingZeros(tmp)) )
			result = false;
		
		System.out.println("Test Successful? " + result);
		return result;
	}
	
}
